#include "U2HardwareBufferManager.h"
#include "U2VertexIndexData.h"
#include "U2LogManager.h"


U2EG_NAMESPACE_USING


//-----------------------------------------------------------------------
template<> U2HardwareBufferManager* U2Singleton<U2HardwareBufferManager>::s_pSingleton = 0;

U2HardwareBufferManager* U2HardwareBufferManager::getSingletonPtr(void)
{
    return s_pSingleton;
}

U2HardwareBufferManager& U2HardwareBufferManager::getSingleton(void)
{  
    assert( s_pSingleton );
    return ( *s_pSingleton );  
}


//---------------------------------------------------------------------
U2HardwareBufferManager::U2HardwareBufferManager(U2HardwareBufferManagerBase* imp)
	: U2HardwareBufferManagerBase(), mImpl(imp)
{

}
//---------------------------------------------------------------------
U2HardwareBufferManager::~U2HardwareBufferManager()
{
	// mImpl must be deleted by the creator
}
//---------------------------------------------------------------------
//---------------------------------------------------------------------
// Free temporary vertex buffers every 5 minutes on 100fps
const size_t U2HardwareBufferManagerBase::UNDER_USED_FRAME_THRESHOLD = 30000;
const size_t U2HardwareBufferManagerBase::EXPIRED_DELAY_FRAME_THRESHOLD = 5;
//-----------------------------------------------------------------------
U2HardwareBufferManagerBase::U2HardwareBufferManagerBase()
    : mUnderUsedFrameCount(0)
{
}
//-----------------------------------------------------------------------
U2HardwareBufferManagerBase::~U2HardwareBufferManagerBase()
{
    // Clear vertex/index buffer list first, avoid destroyed notify do
    // unnecessary work, and we'll destroy everything here.
	mVertexBuffers.clear();
	mIndexBuffers.clear();

    // Destroy everything
    destroyAllDeclarations();
    destroyAllBindings();
    // No need to destroy main buffers - they will be destroyed by removal of bindings

    // No need to destroy temp buffers - they will be destroyed automatically.
}
//-----------------------------------------------------------------------
U2VertexDeclaration* U2HardwareBufferManagerBase::createVertexDeclaration(void)
{
    U2VertexDeclaration* decl = createVertexDeclarationImpl();
	U2_LOCK_MUTEX(mVertexDeclarationsMutex)
    mVertexDeclarations.insert(decl);
    return decl;
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::destroyVertexDeclaration(U2VertexDeclaration* decl)
{
	U2_LOCK_MUTEX(mVertexDeclarationsMutex)
    mVertexDeclarations.erase(decl);
    destroyVertexDeclarationImpl(decl);
}
//-----------------------------------------------------------------------
U2VertexBufferBinding* U2HardwareBufferManagerBase::createVertexBufferBinding(void)
{
	U2VertexBufferBinding* ret = createVertexBufferBindingImpl();
	U2_LOCK_MUTEX(mVertexBufferBindingsMutex)
	mVertexBufferBindings.insert(ret);
	return ret;
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::destroyVertexBufferBinding(U2VertexBufferBinding* binding)
{
	U2_LOCK_MUTEX(mVertexBufferBindingsMutex)
	mVertexBufferBindings.erase(binding);
	destroyVertexBufferBindingImpl(binding);
}
//-----------------------------------------------------------------------
U2VertexDeclaration* U2HardwareBufferManagerBase::createVertexDeclarationImpl(void)
{
    return U2_NEW U2VertexDeclaration();
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::destroyVertexDeclarationImpl(U2VertexDeclaration* decl)
{
    U2_DELETE decl;
}
//-----------------------------------------------------------------------
U2VertexBufferBinding* U2HardwareBufferManagerBase::createVertexBufferBindingImpl(void)
{
	return U2_NEW U2VertexBufferBinding();
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::destroyVertexBufferBindingImpl(U2VertexBufferBinding* binding)
{
	U2_DELETE binding;
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::destroyAllDeclarations(void)
{
	U2_LOCK_MUTEX(mVertexDeclarationsMutex)
    VertexDeclarationList::iterator decl;
    for (decl = mVertexDeclarations.begin(); decl != mVertexDeclarations.end(); ++decl)
    {
        destroyVertexDeclarationImpl(*decl);
    }
    mVertexDeclarations.clear();
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::destroyAllBindings(void)
{
	U2_LOCK_MUTEX(mVertexBufferBindingsMutex)
    VertexBufferBindingList::iterator bind;
    for (bind = mVertexBufferBindings.begin(); bind != mVertexBufferBindings.end(); ++bind)
    {
        destroyVertexBufferBindingImpl(*bind);
    }
    mVertexBufferBindings.clear();
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::registerVertexBufferSourceAndCopy(
		const U2HardwareVertexBufferSharedPtr& sourceBuffer,
		const U2HardwareVertexBufferSharedPtr& copy)
{
	U2_LOCK_MUTEX(mTempBuffersMutex)
	// Add copy to free temporary vertex buffers
    mFreeTempVertexBufferMap.insert(
        FreeTemporaryVertexBufferMap::value_type(sourceBuffer.get(), copy));
}
//-----------------------------------------------------------------------
U2HardwareVertexBufferSharedPtr 
U2HardwareBufferManagerBase::allocateVertexBufferCopy(
    const U2HardwareVertexBufferSharedPtr& sourceBuffer, 
    BufferLicenseType licenseType, U2HardwareBufferLicensee* licensee,
    bool copyData)
{
	// pre-lock the mVertexBuffers mutex, which would usually get locked in
	//  makeBufferCopy / createVertexBuffer
	// this prevents a deadlock in _notifyVertexBufferDestroyed
	// which locks the same mutexes (via other methods) but in reverse order
	U2_LOCK_MUTEX(mVertexBuffersMutex)
	{
		U2_LOCK_MUTEX(mTempBuffersMutex)
		U2HardwareVertexBufferSharedPtr vbuf;

		// Locate existing buffer copy in temporary vertex buffers
		FreeTemporaryVertexBufferMap::iterator i = 
			mFreeTempVertexBufferMap.find(sourceBuffer.get());
		if (i == mFreeTempVertexBufferMap.end())
		{
			// copy buffer, use shadow buffer and make dynamic
			vbuf = makeBufferCopy(
				sourceBuffer, 
				U2HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY_DISCARDABLE, 
				true);
		}
		else
		{
			// Allocate existing copy
			vbuf = i->second;
			mFreeTempVertexBufferMap.erase(i);
		}

		// Copy data?
		if (copyData)
		{
			vbuf->copyData(*(sourceBuffer.get()), 0, 0, sourceBuffer->getSizeInBytes(), true);
		}

		// Insert copy into licensee list
		mTempVertexBufferLicenses.insert(
			TemporaryVertexBufferLicenseMap::value_type(
				vbuf.get(),
				U2VertexBufferLicense(sourceBuffer.get(), licenseType, EXPIRED_DELAY_FRAME_THRESHOLD, vbuf, licensee)));
		return vbuf;
	}

}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::releaseVertexBufferCopy(
    const U2HardwareVertexBufferSharedPtr& bufferCopy)
{
	U2_LOCK_MUTEX(mTempBuffersMutex)

	TemporaryVertexBufferLicenseMap::iterator i =
        mTempVertexBufferLicenses.find(bufferCopy.get());
    if (i != mTempVertexBufferLicenses.end())
    {
        const U2VertexBufferLicense& vbl = i->second;

        vbl.licensee->licenseExpired(vbl.buffer.get());

        mFreeTempVertexBufferMap.insert(
            FreeTemporaryVertexBufferMap::value_type(vbl.originalBufferPtr, vbl.buffer));
        mTempVertexBufferLicenses.erase(i);
    }
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::touchVertexBufferCopy(
        const U2HardwareVertexBufferSharedPtr& bufferCopy)
{
	U2_LOCK_MUTEX(mTempBuffersMutex)
    TemporaryVertexBufferLicenseMap::iterator i =
        mTempVertexBufferLicenses.find(bufferCopy.get());
    if (i != mTempVertexBufferLicenses.end())
    {
        U2VertexBufferLicense& vbl = i->second;
        assert(vbl.licenseType == BLT_AUTOMATIC_RELEASE);

        vbl.expiredDelay = EXPIRED_DELAY_FRAME_THRESHOLD;
    }
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::_freeUnusedBufferCopies(void)
{
	U2_LOCK_MUTEX(mTempBuffersMutex)
    size_t numFreed = 0;

    // Free unused temporary buffers
    FreeTemporaryVertexBufferMap::iterator i;
    i = mFreeTempVertexBufferMap.begin();
    while (i != mFreeTempVertexBufferMap.end())
    {
        FreeTemporaryVertexBufferMap::iterator icur = i++;
        // Free the temporary buffer that referenced by ourself only.
        // TODO: Some temporary buffers are bound to vertex buffer bindings
        // but not checked out, need to sort out method to unbind them.
        if (icur->second.useCount() <= 1)
        {
            ++numFreed;
            mFreeTempVertexBufferMap.erase(icur);
        }
    }

    U2StringUtil::U2StrStreamType str;
    if (numFreed)
    {
        str << "U2HardwareBufferManager: Freed " << numFreed << " unused temporary vertex buffers.";
    }
    else
    {
        str << "U2HardwareBufferManager: No unused temporary vertex buffers found.";
    }
    U2LogManager::getSingleton().logMessage(str.str(), LML_TRIVIAL);
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::_releaseBufferCopies(bool forceFreeUnused)
{
	U2_LOCK_MUTEX(mTempBuffersMutex)
    size_t numUnused = mFreeTempVertexBufferMap.size();
    size_t numUsed = mTempVertexBufferLicenses.size();

    // Erase the copies which are automatic licensed out
    TemporaryVertexBufferLicenseMap::iterator i;
    i = mTempVertexBufferLicenses.begin(); 
    while (i != mTempVertexBufferLicenses.end()) 
    {
        TemporaryVertexBufferLicenseMap::iterator icur = i++;
        U2VertexBufferLicense& vbl = icur->second;
        if (vbl.licenseType == BLT_AUTOMATIC_RELEASE &&
            (forceFreeUnused || --vbl.expiredDelay <= 0))
        {
			vbl.licensee->licenseExpired(vbl.buffer.get());

            mFreeTempVertexBufferMap.insert(
                FreeTemporaryVertexBufferMap::value_type(vbl.originalBufferPtr, vbl.buffer));
            mTempVertexBufferLicenses.erase(icur);
        }
    }

    // Check whether or not free unused temporary vertex buffers.
    if (forceFreeUnused)
    {
        _freeUnusedBufferCopies();
        mUnderUsedFrameCount = 0;
    }
    else
    {
        if (numUsed < numUnused)
        {
            // Free temporary vertex buffers if too many unused for a long time.
            // Do overall temporary vertex buffers instead of per source buffer
            // to avoid overhead.
            ++mUnderUsedFrameCount;
            if (mUnderUsedFrameCount >= UNDER_USED_FRAME_THRESHOLD)
            {
                _freeUnusedBufferCopies();
                mUnderUsedFrameCount = 0;
            }
        }
        else
        {
            mUnderUsedFrameCount = 0;
        }
    }
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::_forceReleaseBufferCopies(
    const U2HardwareVertexBufferSharedPtr& sourceBuffer)
{
    _forceReleaseBufferCopies(sourceBuffer.get());
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::_forceReleaseBufferCopies(
    U2HardwareVertexBuffer* sourceBuffer)
{
	U2_LOCK_MUTEX(mTempBuffersMutex)
    // Erase the copies which are licensed out
    TemporaryVertexBufferLicenseMap::iterator i;
    i = mTempVertexBufferLicenses.begin();
    while (i != mTempVertexBufferLicenses.end()) 
    {
        TemporaryVertexBufferLicenseMap::iterator icur = i++;
        const U2VertexBufferLicense& vbl = icur->second;
        if (vbl.originalBufferPtr == sourceBuffer)
        {
            // Just tell the owner that this is being released
            vbl.licensee->licenseExpired(vbl.buffer.get());

            mTempVertexBufferLicenses.erase(icur);
        }
    }

    // Erase the free copies
    //
    // Why we need this unusual code? It's for resolve reenter problem.
    //
    // Using mFreeTempVertexBufferMap.erase(sourceBuffer) directly will
    // cause reenter into here because vertex buffer destroyed notify.
    // In most time there are no problem. But when sourceBuffer is the
    // last item of the mFreeTempVertexBufferMap, some STL multimap
    // implementation (VC and STLport) will call to clear(), which will
    // causing intermediate state of mFreeTempVertexBufferMap, in that
    // time destroyed notify back to here cause illegal accessing in
    // the end.
    //
    // For safely reason, use following code to resolve reenter problem.
    //
    typedef FreeTemporaryVertexBufferMap::iterator _Iter;
    std::pair<_Iter, _Iter> range = mFreeTempVertexBufferMap.equal_range(sourceBuffer);
    if (range.first != range.second)
    {
        std::list<U2HardwareVertexBufferSharedPtr> holdForDelayDestroy;
        for (_Iter it = range.first; it != range.second; ++it)
        {
            if (it->second.useCount() <= 1)
            {
                holdForDelayDestroy.push_back(it->second);
            }
        }

        mFreeTempVertexBufferMap.erase(range.first, range.second);

        // holdForDelayDestroy will destroy auto.
    }
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::_notifyVertexBufferDestroyed(U2HardwareVertexBuffer* buf)
{
	U2_LOCK_MUTEX(mVertexBuffersMutex)

	VertexBufferList::iterator i = mVertexBuffers.find(buf);
	if (i != mVertexBuffers.end())
	{
        // release vertex buffer copies
		mVertexBuffers.erase(i);
        _forceReleaseBufferCopies(buf);
	}
}
//-----------------------------------------------------------------------
void U2HardwareBufferManagerBase::_notifyIndexBufferDestroyed(U2HardwareIndexBuffer* buf)
{
	U2_LOCK_MUTEX(mIndexBuffersMutex)

	IndexBufferList::iterator i = mIndexBuffers.find(buf);
	if (i != mIndexBuffers.end())
	{
		mIndexBuffers.erase(i);
	}
}
//-----------------------------------------------------------------------
U2HardwareVertexBufferSharedPtr 
U2HardwareBufferManagerBase::makeBufferCopy(
    const U2HardwareVertexBufferSharedPtr& source,
    U2HardwareBuffer::Usage usage, bool useShadowBuffer)
{
    return this->createVertexBuffer(
        source->getVertexSize(), 
        source->getNumVertices(),
        usage, useShadowBuffer);
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
U2TempBlendedBufferInfo::~U2TempBlendedBufferInfo(void)
{
    // check that temp buffers have been released
    if (!destPositionBuffer.isNull())
        destPositionBuffer->getManager()->releaseVertexBufferCopy(destPositionBuffer);
    if (!destNormalBuffer.isNull())
        destNormalBuffer->getManager()->releaseVertexBufferCopy(destNormalBuffer);

}
//-----------------------------------------------------------------------------
void U2TempBlendedBufferInfo::extractFrom(const U2VertexData* sourceData)
{
    // Release old buffer copies first
    if (!destPositionBuffer.isNull())
    {
        destPositionBuffer->getManager()->releaseVertexBufferCopy(destPositionBuffer);
        assert(destPositionBuffer.isNull());
    }
    if (!destNormalBuffer.isNull())
    {
        destNormalBuffer->getManager()->releaseVertexBufferCopy(destNormalBuffer);
        assert(destNormalBuffer.isNull());
    }

    U2VertexDeclaration* decl = sourceData->vertexDeclaration;
    U2VertexBufferBinding* bind = sourceData->vertexBufferBinding;
    const U2VertexElement *posElem = decl->findElementBySemantic(VES_POSITION);
    const U2VertexElement *normElem = decl->findElementBySemantic(VES_NORMAL);

    assert(posElem && "Positions are required");

    posBindIndex = posElem->getSource();
    srcPositionBuffer = bind->getBuffer(posBindIndex);

    if (!normElem)
    {
        posNormalShareBuffer = false;
        srcNormalBuffer.setNull();
    }
    else
    {
        normBindIndex = normElem->getSource();
        if (normBindIndex == posBindIndex)
        {
            posNormalShareBuffer = true;
            srcNormalBuffer.setNull();
        }
        else
        {
            posNormalShareBuffer = false;
            srcNormalBuffer = bind->getBuffer(normBindIndex);
        }
    }
}
//-----------------------------------------------------------------------------
void U2TempBlendedBufferInfo::checkoutTempCopies(bool positions, bool normals)
{
    bindPositions = positions;
    bindNormals = normals;

    if (positions && destPositionBuffer.isNull())
    {
        destPositionBuffer = srcPositionBuffer->getManager()->allocateVertexBufferCopy(srcPositionBuffer, 
            U2HardwareBufferManagerBase::BLT_AUTOMATIC_RELEASE, this);
    }
    if (normals && !posNormalShareBuffer && !srcNormalBuffer.isNull() && destNormalBuffer.isNull())
    {
        destNormalBuffer = srcNormalBuffer->getManager()->allocateVertexBufferCopy(srcNormalBuffer, 
            U2HardwareBufferManagerBase::BLT_AUTOMATIC_RELEASE, this);
    }
}
//-----------------------------------------------------------------------------
bool U2TempBlendedBufferInfo::buffersCheckedOut(bool positions, bool normals) const
{
    if (positions || (normals && posNormalShareBuffer))
    {
        if (destPositionBuffer.isNull())
            return false;

        destPositionBuffer->getManager()->touchVertexBufferCopy(destPositionBuffer);
    }

    if (normals && !posNormalShareBuffer)
    {
        if (destNormalBuffer.isNull())
            return false;

        destNormalBuffer->getManager()->touchVertexBufferCopy(destNormalBuffer);
    }

	return true;
}
//-----------------------------------------------------------------------------
void U2TempBlendedBufferInfo::bindTempCopies(U2VertexData* targetData, bool suppressHardwareUpload)
{
    this->destPositionBuffer->suppressHardwareUpdate(suppressHardwareUpload);
    targetData->vertexBufferBinding->setBinding(
        this->posBindIndex, this->destPositionBuffer);
    if (bindNormals && !posNormalShareBuffer && !destNormalBuffer.isNull())
    {
        this->destNormalBuffer->suppressHardwareUpdate(suppressHardwareUpload);
        targetData->vertexBufferBinding->setBinding(
            this->normBindIndex, this->destNormalBuffer);
    }
}
//-----------------------------------------------------------------------------
void U2TempBlendedBufferInfo::licenseExpired(U2HardwareBuffer* buffer)
{
    assert(buffer == destPositionBuffer.get()
        || buffer == destNormalBuffer.get());

    if (buffer == destPositionBuffer.get())
        destPositionBuffer.setNull();
    if (buffer == destNormalBuffer.get())
        destNormalBuffer.setNull();
}

